import { defined } from '../../Source/Cesium.js';
import { getTimestamp } from '../../Source/Cesium.js';
import { FrameRateMonitor } from '../../Source/Cesium.js';
import createScene from '../createScene.js';

describe('Scene/FrameRateMonitor', function() {

    var scene;
    beforeAll(function() {
        scene = createScene();
    });

    afterAll(function() {
        scene.destroyForSpecs();
    });

    var monitor;
    afterEach(function() {
        if (defined(monitor)) {
            monitor.destroy();
            monitor = undefined;
        }
    });

    function spinWait(milliseconds) {
        var endTime = getTimestamp() + milliseconds;
        /*eslint-disable no-empty*/
        while (getTimestamp() < endTime) {
        }
        /*eslint-enable no-empty*/
    }

    it('throws when constructed without a scene', function() {
        expect(function() {
            monitor = new FrameRateMonitor();
        }).toThrowDeveloperError();

        expect(function() {
            monitor = new FrameRateMonitor({});
        }).toThrowDeveloperError();
    });

    it('can be constructed with just a scene', function() {
        monitor = new FrameRateMonitor({
            scene : scene
        });

        expect(monitor.samplingWindow).toBe(5.0);
        expect(monitor.quietPeriod).toBe(2.0);
        expect(monitor.warmupPeriod).toBe(5.0);
        expect(monitor.minimumFrameRateDuringWarmup).toBe(4);
        expect(monitor.minimumFrameRateAfterWarmup).toBe(8);
        expect(monitor.scene).toBe(scene);
        expect(monitor.lowFrameRate.numberOfListeners).toBe(0);
        expect(monitor.nominalFrameRate.numberOfListeners).toBe(0);
    });

    it('honors parameters to the constructor', function() {
        monitor = new FrameRateMonitor({
            scene : scene,
            samplingWindow : 3.0,
            quietPeriod : 1.0,
            warmupPeriod : 6.0,
            minimumFrameRateDuringWarmup : 1,
            minimumFrameRateAfterWarmup : 2
        });

        expect(monitor.samplingWindow).toBe(3.0);
        expect(monitor.quietPeriod).toBe(1.0);
        expect(monitor.warmupPeriod).toBe(6.0);
        expect(monitor.minimumFrameRateDuringWarmup).toBe(1);
        expect(monitor.minimumFrameRateAfterWarmup).toBe(2);
        expect(monitor.scene).toBe(scene);
    });

    it('raises the lowFrameRate event on low frame rate', function() {
        monitor = new FrameRateMonitor({
            scene : scene,
            quietPeriod : 0.001,
            warmupPeriod : 0.001,
            samplingWindow : 0.001,
            minimumFrameRateDuringWarmup : 1000,
            minimumFrameRateAfterWarmup : 1000
        });

        var spyListener = jasmine.createSpy('listener');
        monitor.lowFrameRate.addEventListener(spyListener);

        // Rendering once starts the quiet period
        scene.render();

        // Wait until we're well past the end of the quiet period.
        spinWait(2);

        // Rendering again records our first sample.
        scene.render();

        // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
        spinWait(2);

        // Record our second sample.  The monitor should notice that our frame rate is too low.
        scene.render();

        expect(monitor.lastFramesPerSecond).toBeLessThan(1000);
        expect(spyListener).toHaveBeenCalled();
    });

    it('does not monitor frame rate while paused', function() {
        monitor = new FrameRateMonitor({
            scene : scene,
            quietPeriod : 0.001,
            warmupPeriod : 0.001,
            samplingWindow : 0.001,
            minimumFrameRateDuringWarmup : 1000,
            minimumFrameRateAfterWarmup : 1000
        });

        var spyListener = jasmine.createSpy('listener');
        monitor.lowFrameRate.addEventListener(spyListener);

        // Rendering once starts the quiet period
        scene.render();

        // Wait until we're well past the end of the quiet period.
        spinWait(2);

        // Rendering again records our first sample.
        scene.render();

        monitor.pause();

        // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
        spinWait(2);

        // Record our second sample.  The monitor would notice that our frame rate is too low,
        // but it's paused.
        scene.render();

        monitor.unpause();

        scene.render();

        expect(spyListener).not.toHaveBeenCalled();
    });

    it('pausing multiple times requires unpausing multiple times', function() {
        monitor = new FrameRateMonitor({
            scene : scene,
            quietPeriod : 0.001,
            warmupPeriod : 0.001,
            samplingWindow : 0.001,
            minimumFrameRateDuringWarmup : 1000,
            minimumFrameRateAfterWarmup : 1000
        });

        var spyListener = jasmine.createSpy('listener');
        monitor.lowFrameRate.addEventListener(spyListener);

        monitor.pause();
        monitor.pause();
        monitor.unpause();

        // Rendering once starts the quiet period
        scene.render();

        // Wait until we're well past the end of the quiet period.
        spinWait(2);

        // Rendering again records our first sample.
        scene.render();

        // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
        spinWait(2);

        // Record our second sample.  The monitor would notice that our frame rate is too low,
        // but it's paused.
        scene.render();

        monitor.unpause();

        scene.render();

        expect(spyListener).not.toHaveBeenCalled();
    });

    it('does not report a low frame rate during the quiet period', function() {
        monitor = new FrameRateMonitor({
            scene : scene,
            quietPeriod : 1.0,
            warmupPeriod : 0.001,
            samplingWindow : 0.001,
            minimumFrameRateDuringWarmup : 1000,
            minimumFrameRateAfterWarmup : 1000
        });

        var spyListener = jasmine.createSpy('listener');
        monitor.lowFrameRate.addEventListener(spyListener);

        // Rendering once starts the quiet period
        scene.render();

        // Wait well over a millisecond, which is the maximum frame time allowed by this instance.
        spinWait(2);

        // Render again.  Even though our frame rate is too low, the monitor shouldn't raise the event because we're in the quiet period.
        scene.render();

        expect(spyListener).not.toHaveBeenCalled();
    });

    it('the nominalFrameRate event is raised after the warmup period if the frame rate returns to nominal', function() {
        monitor = new FrameRateMonitor({
            scene : scene,
            quietPeriod : 0.001,
            warmupPeriod : 0.001,
            samplingWindow : 0.001,
            minimumFrameRateDuringWarmup : 10,
            minimumFrameRateAfterWarmup : 10
        });

        var lowListener = jasmine.createSpy('lowFrameRate');
        monitor.lowFrameRate.addEventListener(lowListener);

        var nominalListener = jasmine.createSpy('nominalFrameRate');
        monitor.nominalFrameRate.addEventListener(nominalListener);

        // Rendering once starts the quiet period
        scene.render();

        // Wait until we're well past the end of the quiet period.
        spinWait(2);

        // Rendering again records our first sample.
        scene.render();

        // Wait 120 millseconds, which is over the maximum frame time allowed by this instance.
        spinWait(120);

        // Record our second sample.  The monitor should notice that our frame rate is too low.
        scene.render();

        expect(monitor.lastFramesPerSecond).toBeLessThan(10);
        expect(lowListener).toHaveBeenCalled();

        // Render as fast as possible for a samplingWindow, quietPeriod, and warmupPeriod.
        var endTime = getTimestamp() + 50;
        while (getTimestamp() < endTime) {
            scene.render();
        }

        // The nominalFrameRate event should have been raised.
        expect(monitor.lastFramesPerSecond).toBeGreaterThanOrEqualTo(10);
        expect(nominalListener).toHaveBeenCalled();
    });
}, 'WebGL');
